/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2015年9月18日
 * V4.0
 */
package com.jphenix.service.communicate.util;

import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.jphenix.service.communicate.exception.RemoteException;
import com.jphenix.share.lang.SBoolean;
import com.jphenix.share.lang.SDouble;
import com.jphenix.share.lang.SInteger;
import com.jphenix.share.lang.SLong;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.standard.docs.ClassInfo;

/**
 * 数据处理工具类
 * 
 * 数据类型标识定义：
 * 
 *   0  String
 *   1  Integer
 *   2  Long
 *   3  Float
 *   4  Double
 *   5  Boolean
 *   6  Map
 *   7  List
 *   8  Bean
 *   
 *   特殊信息 0x07+异常代码+0x0B+异常信息+0x03
 *   
 *   说明： Bean类型，框架读直接取内部的变量值，传输到另外一端后，
 *               框架直接将直反射到新构造的bean变量中。而不是采用传统
 *               的通过Get Set 方法设置值，因为大部分情况下，这两个方法
 *               只是单纯的为变量复制，并没有在这两个方法中做什么特殊
 *               处理，而且每个变量都要写这两个方法，烦都烦死。
 *               
 *   
 * 数据传输规则定义：
 * 
 *  基本格式：
 *   0x01+数据类型（1字节 如果为bean，后面还跟着bean的类路径）+0x02+数据内容+0x03
 *   
 *   数据内容：
 *   1.  如果数据是基本类型，直接就是数据值
 *   2. 如果是map，格式为：
 *          key1+0x0B+value1+0x1E+key2+0x0B+value2
 *          
 *          key 为字符串类型（只支持字符串）
 *          value  上面定义的基本格式（嵌套）
 *          
 *   3. 如果是 list，格式为：
 *         value1+0x1E+value2
 *         
 *         value 上面定义的基本格式（嵌套）
 *         
 *    4. 如果是bean，格式为：
 *          0x01+7+类路径+0x02+以下格式+0x03
 *          
 *          变量名1+0x0B+变量值1+0x1E+变量名2+0x0B+变量值2
 * 
 * 特殊字符说明：
 * 
 *    00 0x00 空
 *    01  0x01 数据开始（内部数据开始）
 *    02 0x02 数据类型与数据体分隔符 （数据体开始）
 *    03 0x03 数据结束（内部数据结束）
 *    04 0x04 传输结束（用在传输层，数据包中不会存在这个字符）
 *    07 0x07 异常标识符（后面跟着异常代码和异常信息）
 *    11  0x0B 数据主键与数据值分隔符（bean变量名与变量值分隔符）
 *    30 0x1E 数据段分隔符（bean变量信息分隔符）
 *    
 * 2019-04-09 用SDouble替换了SFloat处理类
 * 
 * @author 马宝刚
 * 2015年9月18日
 */
@ClassInfo({"2019-04-09 11:12","数据处理工具类"})
public class DataUtil {

    
    /**
     * 将数据对象转换为字节数组
     * @param dataMap 数据对象
     * @return 字节数组
     * 2015年9月18日
     * @author 马宝刚
     */
    @SuppressWarnings("rawtypes")
    public static byte[] dataToBytes(Map dataMap) {
        //构建返回值
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            fixMap(dataMap,bos); //处理map容器
        }catch(Exception e) {
            e.printStackTrace();
            bos = new ByteArrayOutputStream();
            try {
                bos.write(new byte[] {0x07,'-','9','8',0x0B});
                bos.write(e.toString().getBytes());
                bos.write(0x03);
            }catch(Exception e2) {}
            return bos.toByteArray();
        }
        return bos.toByteArray();
    }
    
    /**
     * 处理bean数据
     * @param dataBean bean对象
     * @param bos 输出流
     * @throws Exception 异常
     * 2015年9月22日
     * @author 马宝刚
     */
    private static void fixBean(Object dataBean,ByteArrayOutputStream bos) throws Exception {
        if(dataBean==null) {
            bos.write(new byte[] {0x01,'8',0x02,0x00,0x03});
            return;
        }else {
            bos.write(new byte[] {0x01,'8'});
            bos.write(dataBean.getClass().getName().getBytes());
            bos.write(0x02);
            //类中所有的类变量
            Field[] fields = dataBean.getClass().getDeclaredFields();
            Object value; //值
            for(int i=0;i<fields.length;i++) {
                if(i>0) {
                    bos.write(0x1E);
                }
                fields[i].setAccessible(true);
                value = fields[i].get(dataBean);
                bos.write(fields[i].getName().getBytes());
                bos.write(0x0B);
                fixObject(value,bos);
            }
        }
        bos.write(0x03);
    }
    
    /**
     * 将对象值转换为字节流
     * @param value 对象值
     * @param bos 字节流
     * @throws Exception 异常
     * 2015年9月24日
     * @author 马宝刚
     */
    @SuppressWarnings("rawtypes")
    private static void fixObject(Object value,ByteArrayOutputStream bos) throws Exception {
        if(value==null) {
            bos.write(0x00);
        }else if(value instanceof String) {
            bos.write(new byte[] {0x01,'0',0x02});
            bos.write(value.toString().getBytes());
            bos.write(0x03);
        }else if(value instanceof Integer) {
            bos.write(new byte[] {0x01,'1',0x02});
            bos.write(value.toString().getBytes());
            bos.write(0x03);
        }else if(value instanceof Long) {
            bos.write(new byte[] {0x01,'2',0x02});
            bos.write(value.toString().getBytes());
            bos.write(0x03);
        }else if(value instanceof Float) {
            bos.write(new byte[] {0x01,'3',0x02});
            bos.write(value.toString().getBytes());
            bos.write(0x03);
        }else if(value instanceof Double) {
            bos.write(new byte[] {0x01,'4',0x02});
            bos.write(value.toString().getBytes());
            bos.write(0x03);
        }else if(value instanceof Boolean) {
            bos.write(new byte[] {0x01,'5',0x02});
            if(((Boolean)value).booleanValue()) {
                bos.write('1');
            }else {
                bos.write('0');
            }
            bos.write(0x03);
        }else if(value instanceof Map) {
            fixMap((Map)value,bos);
        }else if(value instanceof List) {
            fixList((List)value,bos);
        }else {
            fixBean(value,bos);
        }
    }
    
    /**
     * 处理list数据
     * @param dataList list对象
     * @param bos 输出流
     * @throws Exception 异常
     * 2015年9月22日
     * @author 马宝刚
     */
    @SuppressWarnings("rawtypes")
    private static void fixList(List dataList,ByteArrayOutputStream bos) throws Exception {
        bos.write(new byte[] {0x01,'7',0x02});
        if(dataList==null) {
            bos.write(0x00);
        }else {
            //类中所有的类变量
            Object value; //值
            for(int i=0;i<dataList.size();i++) {
                if(i>0) {
                    bos.write(0x1E);
                }
                value = dataList.get(i);
                fixObject(value,bos);
            }
        }
        bos.write(0x03);
    }
    
    /**
     * 处理map数据
     * @param dataMap map对象
     * @param bos 输出流
     * @throws Exception 异常
     * 2015年9月22日
     * @author 马宝刚
     */
    @SuppressWarnings("rawtypes")
    private static void fixMap(Map dataMap,ByteArrayOutputStream bos) throws Exception {
        bos.write(new byte[] {0x01,'6',0x02});
        if(dataMap==null) {
            bos.write(0x00);
        }else {
            //获取容器主键序列
            List<String> keyList = BaseUtil.getMapKeyList(dataMap);
            String ele; //主键
            Object value; //值
            for(int i=0;i<keyList.size();i++) {
                if(i>0) {
                    bos.write(0x1E);
                }
                ele = keyList.get(i);
                value = dataMap.get(ele);
                bos.write(ele.getBytes());
                bos.write(0x0B);
                fixObject(value,bos);
            }
        }
        bos.write(0x03);
    }
    
    /**
     * 解析数据类
     * @param dataBytes 待解析的数据
     * @param point 解析指针
     * @return 解析后的类实例
     * @throws Exception 异常
     * 2015年9月23日
     * @author 马宝刚
     */
    @SuppressWarnings("rawtypes")
    private static Object[] unfixBean(byte[] dataBytes,int point) throws Exception {
        if(point>=dataBytes.length) {
            return new Object[] {null,point};
        }
        //构建字节缓存
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte ele = dataBytes[point++]; //字节元素
        //判断对象开始
        while(point<dataBytes.length) {
            if(ele==0x07) {
                //处理错误信息并抛出异常
                unfixError(dataBytes,point);
            }else if(ele==0x02) {
                //进入数据体
                break;
            }else if(ele==0x03) {
                //结束符
                return new Object[] {null,point};
            }else if(ele==0x1E) {
                //段结束
                return new Object[] {null,point};
            }else {
                bos.write(ele);
            }
            ele = dataBytes[point++];
        }
        if(point>=dataBytes.length) {
            //溢出，没发现开始标识
            throw new RemoteException(DataUtil.class,"数据解析错误，没有发现开始标识");
        }
        //类路径
        String classPath = bos.toString();
        bos = new ByteArrayOutputStream();
        
        //构建Bean类
        Class beanCls = Class.forName(classPath);
        //构建Bean类实例
        Object bean = beanCls.newInstance();
        
        ele = dataBytes[point++];
        String key = null; //主键
        Object[] values = null; //值  0值  1处理后的指针
        while(point<dataBytes.length) {
            if(ele==0x07) {
                //处理错误信息并抛出异常
                unfixError(dataBytes,point);
            }else if(ele==0x0B) {
                //主键、值分隔符
                key = bos.toString();
                values = unfixObject(dataBytes,point);
                point = SInteger.valueOf(values[1]) ;
                //获取变量对象
                Field field = beanCls.getDeclaredField(key);
                field.setAccessible(true);
                field.set(bean,values[0]);
            }else if(ele==0x1E) {
                //遇到元素分隔符
                bos = new ByteArrayOutputStream();
                key = null;
            }else if(ele==0x03) {
                //数据结束
                return new Object[] {bean,point};
            }else {
                bos.write(ele);
            }
            ele = dataBytes[point++];
        }
        return new Object[] {bean,point};
    }
    
    /**
     * 将字节数组解析为容器对象
     * @param dataMap 容器对象
     * @param datas 待解析的数据
     * @param point 解析指针
     * @throws Exception 异常
     * 2015年9月23日
     * @author 马宝刚
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private static int unfixMap(Map dataMap,byte[] dataBytes,int point) throws Exception {
        if(point>=dataBytes.length) {
            return point;
        }
        point++; //当前指针指向数据体开始位置 0x02
        byte ele = dataBytes[point++]; //字节元素
        //构建字节缓存
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        String key = null; //主键
        Object value = null; //值
        Object[] res = null; //返回值
        while(point<dataBytes.length) {
            if(ele==0x07) {
                //处理错误信息并抛出异常
                unfixError(dataBytes,point);
            }else if(ele==0x0B) {
                //主键、值分隔符
                key = bos.toString();
                res = unfixObject(dataBytes,point);
                value = res[0];
                point = SInteger.valueOf(res[1]);
                dataMap.put(key,value);
            }else if(ele==0x1E) {
                //遇到元素分隔符
                bos = new ByteArrayOutputStream();
                key = null;
            }else if(ele==0x03) {
                //数据结束
                return point;
            }else {
                bos.write(ele);
            }
            ele = dataBytes[point++];
        }
        return point;
    }
    
    
    /**
     * 将字节数组解析为序列对象
     * @param dataList 序列对象
     * @param dataBytes 待解析的数据
     * @param point 解析指针
     * @throws Exception 异常
     * 2015年9月23日
     * @author 马宝刚
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private static int unfixList(List dataList,byte[] dataBytes,int point) throws Exception {
        if(point>=dataBytes.length) {
            return point;
        }
        point++; //该指针指向数据体开始位置 0x02
        if(dataBytes[point]==0x03) {
            return point+1;
        }
        //获取第一个元素
        Object[] res = unfixObject(dataBytes,point); //返回值
        point = SInteger.valueOf(res[1]);
        dataList.add(res[0]);
        byte ele = dataBytes[++point]; //指向下一个字符
        while(point<dataBytes.length) {
            if(ele==0x07) {
                //处理错误信息并抛出异常
                unfixError(dataBytes,point);
            }else if(ele==0x1E) {
                //主键、值分隔符
                ele = dataBytes[point];
                continue;
            }else if(ele==0x03) {
                //数据结束
                return point;
            }else {
                res = unfixObject(dataBytes,point);
                point = SInteger.valueOf(res[1]);
                dataList.add(res[0]);
            }
            ele = dataBytes[point++];
        }
        return point;
    }

    
    /**
     * 将数据字节数组转换为数据对象
     * @param dataBytes 数据字节数组
     * @return 数据对象
     * 2015年9月18日
     * @author 马宝刚
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static Map bytesToMap(byte[] dataBytes) {
        Object res = null; //构建返回值
        try {
            res = unfixObject(dataBytes,0)[0];
        }catch(Exception e) {
            //构建错误信息容器
            Map errMap = new HashMap();
            errMap.put("_status","-98");
            errMap.put("_msg",e.getMessage());
            return errMap;
        }
        if(res==null) {
            //构建错误信息容器
            Map errMap = new HashMap();
            errMap.put("_status","0");
            errMap.put("_msg","返回数据为空");
            return errMap;
        }
        if(!(res instanceof Map)) {
            //构建错误信息容器
            Map errMap = new HashMap();
            errMap.put("_status","-98");
            errMap.put("_msg","解析错误，返回错误的数据类型:["+res+"]");
            return errMap;
        }
        return (Map)res;
    }
    
    /**
     * 解析报文中的错误信息
     * --该方法必抛异常--
     * @param dataBytes 待解析数据
     * @param point 数据指针
     * @throws Exception 异常
     * 2015年9月23日
     * @author 马宝刚
     */
    private static void unfixError(byte[] dataBytes,int point) throws Exception {
        String code = null; //错误代码
        //错误信息缓存
        ByteArrayOutputStream infoBos = new ByteArrayOutputStream();
        byte ele = dataBytes[point++]; //字节元素
        if(ele==0x07) {
            ele = dataBytes[point++];
        }
        while(ele!=0x03 && point<=dataBytes.length) {
            if(ele==0x0B) {
                code = infoBos.toString();
                infoBos.reset();
            }else if(ele>32) {
                infoBos.write(ele);
            }
            ele =  dataBytes[point++]; 
        }
        throw new RemoteException(DataUtil.class,infoBos.toString(),code);
    }
    
    /**
     * 解析数据还原数据对象
     * @param dataBytes 待解析的数据
     * @param point 数据指针
     * @return 解析后的对象
     * @throws Exception 异常
     * 2015年9月23日
     * @author 马宝刚
     */
    @SuppressWarnings("rawtypes")
    private static Object[] unfixObject(byte[] dataBytes,int point) throws Exception {
        if(point>=dataBytes.length) {
            //溢出，没发现开始标识
            throw new RemoteException(DataUtil.class,"数据解析错误，指针溢出");
        }
        byte ele = dataBytes[++point]; //指针从1的位置移到数据类型位置 待处理字节
        //构建数据输出流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        if(ele=='0') {
            //String
            point++;
            point++; //类型标识后，就是内容开始标识 0x02
            ele = dataBytes[point++];
            if(ele==0x00) {
                return new Object[] {null,point};
            }
            while(ele!=0x03) {
                bos.write(ele);
                ele = dataBytes[point++];
            }
            return new Object[] {bos.toString(),point};
        }else if(ele=='1') {
            //Integer
            point++;
            point++; //类型标识后，就是内容开始标识 0x02
            ele = dataBytes[point++];
            if(ele==0x00) {
                return new Object[] {null,point};
            }
            while(ele!=0x03) {
                bos.write(ele);
                ele = dataBytes[point++];
            }
            return new Object[] {SInteger.integerOf(bos.toString()),point};
        }else if(ele=='2') {
            //Long
            point++;
            point++; //类型标识后，就是内容开始标识 0x02
            ele = dataBytes[point++];
            if(ele==0x00) {
                return new Object[] {null,point};
            }
            while(ele!=0x03) {
                bos.write(ele);
                ele = dataBytes[point++];
            }
            return new Object [] {SLong.longOf(bos.toString()),point};
        }else if(ele=='3') {
            //Float（目前用Double代换）
            point++;
            point++; //类型标识后，就是内容开始标识 0x02
            ele = dataBytes[point++];
            if(ele==0x00) {
                return new Object[] {null,point};
            }
            while(ele!=0x03) {
                bos.write(ele);
                ele = dataBytes[point++];
            }
            return new Object[] {SDouble.doubleOf(bos.toString()),point};
        }else if(ele=='4') {
            //Double
            point++;
            point++; //类型标识后，就是内容开始标识 0x02
            ele = dataBytes[point++];
            if(ele==0x00) {
                return new Object[] {null,point};
            }
            while(ele!=0x03) {
                bos.write(ele);
                ele = dataBytes[point++];
            }
            return new Object[] {SDouble.doubleOf(bos.toString()),point};
        }else if(ele=='5') {
            //Boolean
            point++;
            point++; //类型标识后，就是内容开始标识 0x02
            ele = dataBytes[point++];
            if(ele==0x00) {
                return new Object[] {null,point};
            }
            while(ele!=0x03) {
                bos.write(ele);
                ele = dataBytes[point++];
            }
            return new Object[] {SBoolean.booleanOf(bos.toString()),point};
        }else if(ele=='6') {
            //Map
            //构造返回值
            Map reMap = new HashMap();
            point++;
            //point++; //类型标识后，就是内容开始标识 0x02
            //注意：map容器不能掠过0x02标识，因为在方法内部需要处理
            if(dataBytes[point+1]==0x00) {
                return new Object[] {null,point};
            }
            point = unfixMap(reMap,dataBytes,point);
            return new Object[] {reMap,point};
        }else if(ele=='7') {
            //List
            //构造返回值
            List reList = new ArrayList();
            point++;
            //point++; //类型标识后，就是内容开始标识 0x02
            //注意：list容器不能掠过0x02标识，因为在方法内部需要处理
            if(dataBytes[point+1]==0x00) {
                return new Object[] {null,point};
            }
            point = unfixList(reList,dataBytes,point);
            return new Object[] {reList,point};
        }else if(ele=='8') {
            //Bean
            point++;
            //point++; //类型标识后，就是内容开始标识 0x02
            //注意：bean容器不能掠过0x02标识，因为在方法内部需要处理
            if(dataBytes[point+1]==0x00) {
                return new Object[] {null,point};
            }
            return unfixBean(dataBytes,point);
        }else if(ele==0x07) {
            //处理错误信息并抛出异常
            unfixError(dataBytes,point);
            return null; //执行不到这里，上一句会抛出异常
        }else {
            throw new RemoteException(DataUtil.class,"数据解析错误，未知数据类型标识:["+dataBytes[point]+"]");
        }
    }
}
